import {
  getNonceStr,
  getQueryStr,
  loadPrivateKeyFromContent,
  signSha256WithRsaPSS, sm2VerifySign,
  snake2camelJson,
  UniCloudError
} from '../shared'
import protocols from './protocols'

const clientPlatformTypes = ['app-harmony', 'mp-harmony']
const ENDPOINT = 'https://petalpay-developer.cloud.huawei.com.cn'

class HuaweiPaymentBase {
  constructor (options) {
    this.options = options
    this._protocols = protocols

    if (!options.appId) throw new Error('appId is required')
    if (!options.mchId) throw new Error('mchId is required')
    if (!options.mchAuthId) throw new Error('mchAuthId is required')
    if (!options.mchPrivateKey) throw new Error('mchPrivateKey is required')
    // if (!options.platformPublicKey) throw new Error('platformPublicKey is required')
    if (!clientPlatformTypes.includes(options.clientType)) throw new Error(`clientType ${options.clientType} not supported`)

    this._mchPrivateKey = loadPrivateKeyFromContent(options.mchPrivateKey)

    this._isApp = options.clientType === 'app-harmony'
  }

  _sign (data = {}, path) {
    const queryStr = getQueryStr(data, true)

    if (!queryStr && path) {
      return signSha256WithRsaPSS(this._mchPrivateKey.toPEM(), path)
    }

    return signSha256WithRsaPSS(this._mchPrivateKey.toPEM(), path ? `${path}?${queryStr}` : queryStr)
  }

  /**
   * @param action
   * @param data
   * @param method
   * @param headers
   * @returns {Promise<*>}
   * @protected
   */
  async _request ({
    action,
    data,
    method = 'GET',
    headers = {}
  }) {
    const url = `${ENDPOINT}${action}`
    const _headers = Object.assign({}, {
      'content-type': 'application/json'
    }, headers)
    const payMercAuth = {
      callerId: this.options.mchId,
      traceId: getNonceStr(32),
      time: Date.now(),
      authId: this.options.mchAuthId,
      bodySign: this._sign(data, method === 'GET' ? action : null)
    }
    payMercAuth.headerSign = this._sign(payMercAuth)

    _headers.PayMercAuth = JSON.stringify(payMercAuth)

    const res = await uniCloud.httpclient.request(url, {
      method,
      data,
      headers: _headers,
      dataType: 'json',
      timeout: this.options.timeout || 10000
    })

    const {
      data: respData,
      status
    } = res

    if (status !== 200) {
      throw new UniCloudError({
        code: -1,
        message: `${res.res?.message || '请求异常'}(${status})`
      })
    }

    if (respData.resultCode !== '000000') {
      throw new UniCloudError({
        code: respData.resultCode,
        message: respData.resultDesc + (respData.subDesc ? `: ${respData.subDesc}` : '')
      })
    }

    return snake2camelJson(respData)
  }

  /**
   * 生成客户端支付参数
   * @param {String} prepayId
   * @protected
   */
  _getPayParamsByPrepayId (prepayId) {
    const requestOptions = {
      app_id: this.options.appId,
      merc_no: this.options.mchId,
      auth_id: this.options.mchAuthId,
      prepay_id: prepayId,
      noncestr: getNonceStr(),
      timestamp: String(Date.now())
    }

    requestOptions.sign = this._sign(requestOptions)

    return JSON.stringify(requestOptions)
  }

  _verifyResponseSign (body) {
    // 没有配置华为只支付平台公钥，不需要验证
    if (!this.options.platformPublicKey) return true
    const {
      sign,
      ...rest
    } = body
    const kvStr = getQueryStr(rest, true)
    const isVerify = sm2VerifySign(kvStr, sign, this.options.platformPublicKey)

    if (!isVerify) {
      throw new Error('response signature verification failed')
    }
  }
}

class HuaweiPayment extends HuaweiPaymentBase {
  /**
   * 获取支付参数
   * @param params.subject 商品名称
   * @param params.outTradeNo 商户订单号
   * @param params.totalFee 金额
   * @param params.notifyUrl 回调地址
   * @param params.spbillCreateIp 客户端IP
   * @param params.tradeType 交易类型
   * @param params.bizType 业务类型
   */
  async getOrderInfo (params) {
    const {
      tradeType,
      ...rest
    } = params

    const { prepayId } = await this._request({
      action: `/api/v2/aggr/preorder/create/${this._isApp ? 'app' : 'fa'}`,
      method: 'POST',
      data: {
        appId: this.options.appId,
        mercNo: this.options.mchId,
        ...rest
      }
    })

    return this._getPayParamsByPrepayId(prepayId)
  }

  /**
   * 查询订单
   * @param {String} params.sysTransOrderNo 平台订单号
   * @param {String} params.mercOrderNo 商户订单号
   * @returns {Promise<*>}
   */
  async orderQuery (params) {
    return this._request({
      action: params.sysTransOrderNo ? `/api/v2/aggr/transactions/orders/${params.sysTransOrderNo}` : `/api/v2/aggr/transactions/merc-orders/${params.mercOrderNo}`,
      method: 'GET'
    })
  }

  /**
   * 关闭订单
   * @returns {Promise<void>}
   */
  async closeOrder () {
    throw new Error('not supported')
  }

  /**
   * 取消订单
   * @returns {Promise<void>}
   */
  async cancelOrder () {
    throw new Error('not supported')
  }

  /**
   * 退款
   * @param params.sysTransOrderNo 平台订单号
   * @param params.mercOrderNo 商户订单号
   * @param params.mercRefundOrderNo 商户退款单号
   * @param params.refundAmount 退款金额
   * @param params.reason 退款原因
   * @param params.callbackUrl 回调地址
   *
   * @returns {Promise<void>}
   */
  async refund (params) {
    return this._request({
      action: '/api/v1/aggr/transactions/refunds',
      method: 'POST',
      data: params
    })
  }

  /**
   * 退款查询
   * @param params.mercRefundOrderNo 商户退款单号
   * @param params.sysRefundOrderNo 平台退款单号
   * @returns {Promise<*>}
   */
  async refundQuery (params) {
    return this._request({
      action: params.sysRefundOrderNo ? `/api/v1/aggr/transactions/refunds/orders/${params.sysRefundOrderNo}` : `/api/v1/aggr/transactions/refunds/merc-orders/${params.mercRefundOrderNo}`,
      method: 'GET'
    })
  }

  /**
   * 获取通知类型
   */
  async checkNotifyType (event) {
    const body = typeof event.body === 'string' ? JSON.parse(event.body) : event.body
    // 请求合法验证
    this._verifyResponseSign(body)

    const {
      sysTransOrderNo,
      mercOrderNo
    } = body

    if (sysTransOrderNo && mercOrderNo) {
      return 'payment'
    } else {
      return 'refund'
    }
  }

  /**
   * 验证支付通知
   * @param event
   */
  async verifyPaymentNotify (event) {
    const body = typeof event.body === 'string' ? JSON.parse(event.body) : event.body
    const notifyType = await this.checkNotifyType(event)

    if (notifyType !== 'payment') return false

    return body
  }

  /**
   * 验证退款通知
   * @param event
   */
  async verifyRefundNotify (event) {
    const body = typeof event.body === 'string' ? JSON.parse(event.body) : event.body
    const notifyType = await this.checkNotifyType(event)

    if (notifyType !== 'refund') return false

    return body
  }

  /**
   * 异步通知响应成功
   */
  returnNotifySuccess () {
    return {
      resultCode: '000000',
      resultDesc: 'Success.'
    }
  }

  /**
   * 内部函数，未对外开放
   */
  async request (event) {
    const {
      url: action,
      data = {},
      method = 'GET',
      headers
    } = event
    return this._request({
      action,
      method,
      data,
      headers
    })
  }
}

export default HuaweiPayment
